/*
 *   The contents of this file are subject to the Mozilla Public License
 *   Version 1.1 (the "License"); you may not use this file except in
 *   compliance with the License. You may obtain a copy of the License at
 *   http://www.mozilla.org/MPL/
 *
 *   Software distributed under the License is distributed on an "AS IS"
 *   basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 *   License for the specific language governing rights and limitations
 *   under the License.
 *
 *   The Original Code is Matra - the DTD Parser.
 *
 *   The Initial Developer of the Original Code is Conrad S Roche.
 *   Portions created by Conrad S Roche are Copyright (C) Conrad
 *   S Roche. All Rights Reserved.
 *
 *   Alternatively, the contents of this file may be used under the terms
 *   of the GNU GENERAL PUBLIC LICENSE Version 2 or any later version
 *   (the  "[GPL] License"), in which case the
 *   provisions of GPL License are applicable instead of those
 *   above.  If you wish to allow use of your version of this file only
 *   under the terms of the GPL License and not to allow others to use
 *   your version of this file under the MPL, indicate your decision by
 *   deleting  the provisions above and replace  them with the notice and
 *   other provisions required by the GPL License.  If you do not delete
 *   the provisions above, a recipient may use your version of this file
 *   under either the MPL or the GPL License."
 *
 *   [NOTE: The text of this Exhibit A may differ slightly from the text of
 *   the notices in the Source Code files of the Original Code. You should
 *   use the text of this Exhibit A rather than the text found in the
 *   Original Code Source Code for Your Modifications.]
 *
 * Created: Conrad S Roche <derupe at users.sourceforge.net>,  25-Jul-2000
 */

package com.conradroche.matra.tree;

import java.util.Enumeration;
import java.util.Vector;
import java.util.StringTokenizer;
import java.util.Hashtable;

import com.conradroche.dtd.decl.AttDef;
import com.conradroche.matra.Matra;
import com.conradroche.matra.decl.AttList;
import com.conradroche.matra.decl.Attribute;
import com.conradroche.matra.decl.DocType;
import com.conradroche.matra.decl.ElementType;
import com.conradroche.matra.dtdparser.*;
import com.conradroche.matra.exception.*;

/**
 * Utility class to generate the dtd tree.
 *
 * @author: Conrad Roche
 */
public class DTDTree {

	//CR: FIXME: Need to take care of the (#PCDATA | abc) content model.
	//currently it would print a broken tree.

	private DTDParser dtdParser;
	private Vector printedEleList;

	public Hashtable ElementList, AttributeList, EntityList;

/**
 * DTDTree constructor comment.
 *
 * @param parser  a DTDParser object
 *
 * @throws DTDException If the dtd is not completely parsed yet.
 */
public DTDTree(DTDParser parser) throws DTDException {

	dtdParser = parser;
	DocType doctype = parser.getDocType();

	AttributeList = doctype.getAllAttributes();
	EntityList = doctype.getEntityList();
	ElementList = doctype.getElementList();
}
/**
 * Insert the method's description here.
 * @param str java.lang.String
 */
private void print(String str) {

	System.out.print(str);
}
/**
 * Insert the method's description here.
 *
 * @param element java.lang.String
 *
 * @return
 */
private String printAttributes(String element) {

	AttList attlist = (AttList) AttributeList.get(element);

	if(attlist == null)
		return "";

	Enumeration attribs = attlist.getAttribs().elements();

	if( !attribs.hasMoreElements() )
		return "";

	String str = "(";
	while( true ) {
		Attribute currAttrib = (Attribute) attribs.nextElement();
		str += currAttrib.getAttributeName();

		if(currAttrib.getOptionality() == AttDef.OPTIONALITY_IMPLIED) {
			str += "?";
		}

		String defaultVal = currAttrib.getDefaultValue();
		if(defaultVal != null /*&& defaultVal.length() != 0*/) {//commented to take care of the case where the def val is ""
			str += "=\"" + defaultVal + "\"";
		} else {
			String fixedVal = currAttrib.getFixedValue();
			if(fixedVal != null) {
				str += "==\"" + fixedVal + "\"";
			}
		}

		if( attribs.hasMoreElements() )
			str += ", ";
		else
			break;
	}

	str += ")";

	return str;
}
/**
 * Insert the method's description here.
 * @param str java.lang.String
 */
private void println(String str) {

	System.out.println(str);
}
/**
 * Insert the method's description here.
 *
 * @param rootElement java.lang.String
 * @param optionality
 * @param prefix
 * @param ancestors
 *
 * @throws DTDException
 */
private void printTree(String rootElement, String cardinality, String prefix, String ancestors) throws DTDException {

	boolean showRepetitiveStr = false; //show repetitive structure/nodes
	boolean showCardinality = true; //show the cardinality of the element
	String displayedEleName = null;

	if(prefix.equals(""))
		printedEleList = new Vector();

	if(rootElement.equals("#PCDATA"))
		return;

	ElementType root = (ElementType) ElementList.get(rootElement);

	if(root == null)
		throw new DTDException("Null value passed for the root element(" + rootElement + ").");

	displayedEleName = root.getName();
	if(showCardinality)
		displayedEleName += cardinality;

	String newPrefix = prefix.replace('+', ' ');
	newPrefix = newPrefix.replace('-', ' ');

	if(root.isEmptyContentModel()) {
		prefix = prefix.replace('-', '=');
	}

	//if this element is the same as one of its ancestors, then this would
	//lead to an infinite loop! Stop it!
	StringTokenizer st = new StringTokenizer(ancestors, ">");
	while( st.hasMoreTokens() ) {
		String element = st.nextToken();
		if( rootElement.equals(element) ) {
			println( prefix + displayedEleName + " " + printAttributes(rootElement) + " **");
			println( newPrefix);
			return; //inf loop condition
		}
	}

	String suffix = "";
	print( prefix + displayedEleName + " " + printAttributes(rootElement) );

	//if the tree for this node has already been printed, don't print it again.
	if(!showRepetitiveStr && printedEleList.contains(rootElement)) {
		suffix += " -->";
		println(suffix);
		println( newPrefix);
		return;
	}
	println(suffix);

	if(!showRepetitiveStr && root.hasChildren())
		printedEleList.addElement(rootElement);

	if(root.isAnyContentModel()) {
		println( newPrefix + "|");
		println( newPrefix + "+- *");
		println( newPrefix);
		return;
	}

	if(root.hasChildren())
		println( newPrefix + "|");
	else {
		println( newPrefix);
		return;
	}

	ancestors += ">" + rootElement;

	String[] childrenNames = root.getChildrenNames();

	//CR: TODO: Use |= for elements that have EMPTY content model
	for(int i = 0; i < childrenNames.length - 1; i++) {
		printTree(childrenNames[i], root.getChildOptionality(childrenNames[i]), newPrefix + "|- ", ancestors);
	}

	printTree(childrenNames[childrenNames.length - 1], root.getChildOptionality(childrenNames[childrenNames.length - 1]), newPrefix + "+- ", ancestors);

}
/**
 * Prints the trees for this dtd.
 *
 * @throws DTDException If creation of the dtd tree failed.
 */
public void printTrees() throws DTDException {

	Enumeration roots = dtdParser.getDocType().getRootElements();

	if(roots != null) {
		while( roots.hasMoreElements() ) {
			printTree( (String) roots.nextElement(), "", "", "" );
		}
		println("Tree representation of the DTD - generated using Matra " + Matra.version + " (http://matra.sourceforge.net/)");
	} else {
		System.out.println("No root Element found.");
	}

}
}

